/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Xodnizel
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * VRC-7
 *
 */

#include "mapinc.h"

#define vrctemp mapbyte1[0]
static uint8 indox;

#include "emu2413.h"

static int acount = 0;

static OPLL *VRC7Sound = NULL;
static int dwave = 0;

void DoVRC7Sound(void) {
	int32 z, a;

	if (FSettings.soundq >= 1) return;
	z = ((SOUNDTS << 16) / soundtsinc) >> 4;
	a = z - dwave;

	moocow(VRC7Sound, &Wave[dwave], a, 1);

	dwave += a;
}

void UpdateOPLNEO(int32 *Wave, int Count) {
	moocow(VRC7Sound, Wave, Count, 4);
}

void UpdateOPL(int Count) {
	int32 z, a;

	z = ((SOUNDTS << 16) / soundtsinc) >> 4;
	a = z - dwave;

	if (VRC7Sound && a)
		moocow(VRC7Sound, &Wave[dwave], a, 1);

	dwave = 0;
}

static INLINE void DaMirror(int V) {
	int salpo[4] = { MI_V, MI_H, MI_0, MI_1 };
	setmirror(salpo[V & 3]);
}

DECLFW(Mapper85_write) {
	A |= (A & 8) << 1;

	if (A >= 0xa000 && A <= 0xDFFF) {
		// printf("$%04x, $%04x\n",X.PC,A);
		A &= 0xF010;
		{
			int x = ((A >> 4) & 1) | ((A - 0xA000) >> 11);
			mapbyte3[x] = V;
			setchr1(x << 10, V);
		}
	}else if (A == 0x9030) {
		if (FSettings.SndRate) {
			OPLL_writeReg(VRC7Sound, indox, V);
			GameExpSound.Fill = UpdateOPL;
			GameExpSound.NeoFill = UpdateOPLNEO;
		}
	}else switch (A & 0xF010) {
		case 0x8000: mapbyte2[0] = V; setprg8(0x8000, V); break;
		case 0x8010: mapbyte2[1] = V; setprg8(0xa000, V); break;
		case 0x9000: mapbyte2[2] = V; setprg8(0xc000, V); break;
		case 0x9010: indox = V; break;
		case 0xe000: mapbyte2[3] = V; DaMirror(V); break;
		case 0xE010: IRQLatch = V;
			X6502_IRQEnd(FCEU_IQEXT);
			break;
		case 0xF000: IRQa = V & 2;
			vrctemp = V & 1;
			if (V & 2) {
				IRQCount = IRQLatch;
			}
			acount = 0;
			X6502_IRQEnd(FCEU_IQEXT);
			break;
		case 0xf010: if (vrctemp) IRQa = 1;
			else IRQa = 0;
			X6502_IRQEnd(FCEU_IQEXT);
			break;
		}
}

static void FP_FASTAPASS(1) KonamiIRQHook(int a) {
  #define ACBOO 341
//  #define ACBOO ((227*2)+1)
	if (IRQa) {
		acount += a * 3;

		if (acount >= ACBOO) {
 doagainbub: acount -= ACBOO;
			IRQCount++;
			if (IRQCount & 0x100) {
				X6502_IRQBegin(FCEU_IQEXT); IRQCount = IRQLatch;
			}
			if (acount >= ACBOO) goto doagainbub;
		}
	}
}

void Mapper85_StateRestore(int version) {
	int x;

	if (version < 7200) {
		for (x = 0; x < 8; x++)
			mapbyte3[x] = CHRBankList[x];
		for (x = 0; x < 3; x++)
			mapbyte2[x] = PRGBankList[x];
		mapbyte2[3] = (Mirroring < 0x10) ? Mirroring : Mirroring - 0xE;
	}

	for (x = 0; x < 8; x++)
		setchr1(x * 0x400, mapbyte3[x]);
	for (x = 0; x < 3; x++)
		setprg8(0x8000 + x * 8192, mapbyte2[x]);
	DaMirror(mapbyte2[3]);
	//LoadOPL();
}

static void M85SC(void) {
	if (VRC7Sound)
		OPLL_set_rate(VRC7Sound, FSettings.SndRate);
}

static void M85SKill(void) {
	if (VRC7Sound)
		OPLL_delete(VRC7Sound);
	VRC7Sound = NULL;
}

static void VRC7SI(void) {
	GameExpSound.RChange = M85SC;
	GameExpSound.Kill = M85SKill;

	VRC7Sound = OPLL_new(3579545, FSettings.SndRate ? FSettings.SndRate : 44100);
	OPLL_reset(VRC7Sound);
	OPLL_reset(VRC7Sound);
}

void NSFVRC7_Init(void) {
	SetWriteHandler(0x9010, 0x901F, Mapper85_write);
	SetWriteHandler(0x9030, 0x903F, Mapper85_write);
	VRC7SI();
}

void Mapper85_init(void) {
	MapIRQHook = KonamiIRQHook;
	SetWriteHandler(0x8000, 0xffff, Mapper85_write);
	GameStateRestore = Mapper85_StateRestore;
	if (!VROM_size)
		SetupCartCHRMapping(0, CHRRAM, 8192, 1);
	//AddExState(VRC7Instrument, 16, 0, "VC7I");
	//AddExState(VRC7Chan, sizeof(VRC7Chan), 0, "V7CH");
	VRC7SI();
}
